﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;

namespace _Sieci_Email_Klient
{
    enum Folder { Inbox, Junk };
    public enum Command {Login, Logout, Refresh, Send, GetMessage, Delete, MoveToJunk, None}
    public struct UserData
    {
        public string Login;
        public string Domain;
        public int ServerPort;
        public string ServerIp;
    }
    public partial class Inbox : Form
    {
        private Inbox obj;
        string serverIP = "192.168.1.1";
        int serverPort = 1337;
        TcpClient clientSocket = new TcpClient();
        NetworkStream serverStream;

        List<Message> _messages = new List<Message>();
        List<Message> _junkMessages = new List<Message>();
        public string Recepient;
        public string Subject;
        public string Message;
        private string _login;
        private string _domain;
        private bool LoggedOut = false;
        private bool Connected = true;
        Folder currentFolder = Folder.Inbox;

        delegate void RefreshMessageListsCallback(string message);
        delegate void SetMessageTextBoxCallback(string message);
        delegate void CloseCallback();
        public Inbox(UserData data)
        {
            InitializeComponent();
            obj = this;
            _login = data.Login;
            _domain = data.Domain;
            serverPort = data.ServerPort;
            serverIP = data.ServerIp;
            TestMessages();
            string message = "chck#" + _login + "#";
            SendMessage(message, Command.Refresh);
        }

        private void CloseCall()
        {
            if(obj.InvokeRequired)
            {
                CloseCallback close = new CloseCallback(CloseCall);
                obj.Invoke(close);
            }   
            else
            {
                LoggedOut = true;
                Close();
                Application.Exit();
            }
        }
        
        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                /* retrieve the SocketStateObject */
                SocketStateObject state = (SocketStateObject)ar.AsyncState;
                Socket socketFd = state.m_SocketFd;

                /* read data */
                int size = socketFd.EndReceive(ar);

                //if (size > 0)
                //{
                    state.m_StringBuilder.Append(Encoding.ASCII.GetString(state.m_DataBuf, 0, size));

                //    /* get the rest of the data */
                //    socketFd.BeginReceive(state.m_DataBuf, 0, SocketStateObject.BUF_SIZE, 0,
                //                          new AsyncCallback(ReceiveCallback), state);
                //}
                //else
                //{
                    /* all the data has arrived */
                    if (state.m_StringBuilder.Length > 1)
                    {
                        string receivedMessage = state.m_StringBuilder.ToString();
                        switch (state.m_cmd) {
                            case Command.Refresh:
                                RefreshMessageLists(receivedMessage);
                                break;
                            case Command.GetMessage:
                                SetMessageTextBox(receivedMessage);
                                break;
                            case Command.Logout:
                                CloseCall();
                                break;
                            case Command.Send:
                                break;
                            case Command.Delete:
                                RefreshMessageLists(receivedMessage);
                                break;
                            case Command.MoveToJunk:
                                RefreshMessageLists(receivedMessage);
                                break;
                                
                        }
                        /* shutdown and close socket */
                        socketFd.Shutdown(SocketShutdown.Both);
                        socketFd.Close();
                    }
                //}
            }
            catch (Exception exc)
            {
                MessageBox.Show("Exception:\t\n" + exc.Message.ToString());
            }
        }

        private void SendCallBack(IAsyncResult ar)
        {
            SocketStateObject state = (SocketStateObject)ar.AsyncState;
            state.m_SocketFd.EndSend(ar);

            /* create the SocketStateObject */

            /* begin receiving the data */
            state.m_SocketFd.BeginReceive(state.m_DataBuf, 0, SocketStateObject.BUF_SIZE, 0,
                                 new AsyncCallback(ReceiveCallback), state);
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                /* retrieve the socket from the state object */
                SocketStateObject state = (SocketStateObject)ar.AsyncState;

                /* complete the connection */
                state.m_SocketFd.EndConnect(ar);

                /* create the SocketStateObject */

                byte[] bMessage = Encoding.ASCII.GetBytes(state.m_Message);

                state.m_SocketFd.BeginSend(bMessage, 0, sizeof(byte)*bMessage.Length, 0, new AsyncCallback(SendCallBack), state);
               
            }
            catch (Exception exc)
            {
                MessageBox.Show("Exception:\t\n" + exc.Message.ToString());
            }
        }

        private void GetHostEntryCallback(IAsyncResult ar)
        {
            try
            {
                IPHostEntry hostEntry = null;
                IPAddress[] addresses = null;
                Socket socketFd = null;
                IPEndPoint endPoint = null;

                /* complete the DNS query */
                hostEntry = Dns.EndGetHostEntry(ar);
                addresses = hostEntry.AddressList;

                /* create a socket */
                socketFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                SocketStateObject state = (SocketStateObject)ar.AsyncState;

                state.m_SocketFd = socketFd;

                /* remote endpoint for the socket */
                endPoint = new IPEndPoint(addresses[0], Int32.Parse(serverPort.ToString()));

                /* connect to the server */
                state.m_SocketFd.BeginConnect(endPoint, new AsyncCallback(ConnectCallback), state);
            }
            catch (Exception exc)
            {
                MessageBox.Show("Exception:\t\n" + exc.Message.ToString());
                Connected = false;
            }    
        }

        private void TestMessages()
        {
            for (int i = 0; i < 5; i++)
            {
                _messages.Add(new Message
                                  {
                                      From = "marcindobry@hotmail.com",
                                      Id = i,
                                      State = States.Read,
                                      Subject = "Whatever lol dude",
                                      MessageContent = "It fucking works"
                                  });
            }
            _messages.Add(new Message
                              {
                                  From = "marcindobry@hotmail.com",
                                  Id = 18,
                                  State = States.Unread,
                                  Subject = "Whatever lol dude",
                                  MessageContent = "It fucking works"
                              });
            _junkMessages.Add(new Message
                                    {
                                        From = "marcindobry@gmail.com",
                                        Id = 3,
                                        State = States.Junk,
                                        Subject = "Junk",
                                        MessageContent = "Junk Content"
                                    });

        }

        public void ClearGrid()
        {
            for (int ii = 0; ii < inboxGrid.RowCount; ii++)
            {
                for (int j = 0; j < inboxGrid.ColumnCount; j++)
                {
                    inboxGrid[j, ii].Value = "";
                }
            }
        }

        public States ParseState(string state)
        {
            switch (state)
            {
                case "r":
                    return States.Read;
                case "u":
                    return States.Unread;
                case "j":
                    return States.Junk;
                default:
                    return States.Read;
            }
        }
        
        public void RefreshMessageLists(string message){

            if (obj.InvokeRequired)
            {
                RefreshMessageListsCallback refresh = new RefreshMessageListsCallback(RefreshMessageLists);
                obj.Invoke(refresh, message);
            }
            else
            {
                try
                {
                    _junkMessages = new List<Message>();
                    _messages = new List<Message>();
                    string[] splitMessage = message.Split(new Char[] {'#'});
                    for (int i = 0; i < splitMessage.Length - 1; i += 4)
                    {
                        Message tempMessage = new Message
                                                  {
                                                      Id = Convert.ToInt32(splitMessage[i]),
                                                      From = splitMessage[i + 1],
                                                      Subject = splitMessage[i + 2],
                                                      State = ParseState(splitMessage[i + 3])
                                                  };
                        if (tempMessage.State == States.Junk)
                        {
                            _junkMessages.Add(tempMessage);
                        }
                        else
                        {
                            _messages.Add(tempMessage);
                        }
                    }
                    if(currentFolder == Folder.Inbox)
                    {
                        UpdateGrid();
                    }
                    else if (currentFolder == Folder.Junk)
                    {
                        UpdateJunkGrid();
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        private void SetMessageTextBox(string message)
        {
            if(obj.InvokeRequired)
            {
                SetMessageTextBoxCallback set = new SetMessageTextBoxCallback(SetMessageTextBox);
                obj.Invoke(set, message);        
            }
            else
            {
                Message msg = ParseMsg(message);
                messageTextBox.Text = msg.MessageContent;    
            }
            
        }

        private void UpdateGrid()
        {
            ClearGrid();
            inboxGrid.RowCount = _messages.Count;
            DataGridViewCellStyle cellStyle = inboxGrid.DefaultCellStyle;
            Font regularFont = cellStyle.Font;
            Font boldFont = new Font(cellStyle.Font, FontStyle.Bold);
            for (int i = 0; i < _messages.Count; i++)
            {
                if (_messages[i].State == States.Unread)
                {
                    inboxGrid[0, i].Style.Font = boldFont;
                    inboxGrid[1, i].Style.Font = boldFont;
                }
                else if (_messages[i].State == States.Read)
                {
                    inboxGrid[0, i].Style.Font = cellStyle.Font;
                    inboxGrid[1, i].Style.Font = cellStyle.Font;
                }
                inboxGrid[0, i].Value = _messages[i].From;
                inboxGrid[1, i].Value = _messages[i].Subject;
            }
            inboxGrid.ClearSelection();
        }

        private void UpdateJunkGrid()
        {
            ClearGrid();
            inboxGrid.RowCount = _junkMessages.Count;
            for (int i = 0; i < _junkMessages.Count; i++)
            {
                inboxGrid[0, i].Value = _junkMessages[i].From;
                inboxGrid[1, i].Value = _junkMessages[i].Subject;
            }
            inboxGrid.ClearSelection();
        }

        private void LogOut()
        {
            string message = "bye#" + _login + "#";
            SendMessage(message, Command.Logout);
        }

        private void ConnectToServer()
        {
            try
            {
                clientSocket = new TcpClient();
                clientSocket.Connect(serverIP, serverPort);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void DisconnectFromServer()
        {
            try
            {
                //clientSocket.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void SendMessage(string message, Command cmd)
        {
            try
            {
                //serverStream = clientSocket.GetStream();
                //byte[] outStream = Encoding.ASCII.GetBytes(message);
                //serverStream.Write(outStream, 0, outStream.Length);
                ////serverStream.Flush();
                //

                SocketStateObject state = new SocketStateObject();
                state.m_Message = message;
                state.m_cmd = cmd;
                Dns.BeginGetHostEntry(serverIP, new AsyncCallback(GetHostEntryCallback), state);
                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        public string ReceiveMessage()
        {
            //try
            //{
            //    //serverStream = new NetworkStream(clientSocket);
                
            //    string msg = "";
            //    byte[] inStream = new byte[1];
            //    bool quit = false;
            //    while (!quit)
            //    {
            //        int n = serverStream.Read(inStream, 0, 1);
            //        if(inStream[0] == Convert.ToByte('\0'))
            //        {
            //            quit = true;
            //            break;
            //        }
            //        msg += Encoding.ASCII.GetString(inStream);
            //    }
            //    //string returnData = Encoding.ASCII.GetString(inStream);
            //    string returnData = msg;
            //    return returnData;
            //}
            //catch (Exception ex)
            //{
            //    MessageBox.Show(ex.Message);
            //}
            return null;
        }

        private string getSender()
        {
            try
            {
                string result = null;
                int count = 0;
                for (int i = 0; i < inboxGrid.RowCount; i++)
                {
                    if (inboxGrid[0, i].Selected)
                    {
                        count++;
                        if (currentFolder == Folder.Inbox)
                        {
                            result = _messages[i].From;
                        }
                        else
                        {
                            result = _junkMessages[i].From;
                        }
                    }
                }
                if (count > 1)
                {
                    return null;
                }
                return result;
            }
            catch (NullReferenceException ex)
            {
                MessageBox.Show("Select exactly 1 person to reply");
                return null;
            }
        }

        private void newButton_Click(object sender, EventArgs e)
        {
            NewMessage newForm = new NewMessage();
            //System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(startForm));
            if (newForm.ShowDialog() == DialogResult.OK)
            {
                foreach (string Recepient in newForm.Recepients)
                {
                    string messageString = "mail#" + _login + "@" + _domain + "#" + Recepient + "#" + newForm.Subject + "#" +
                                           newForm.Message + "#";
                    SendMessage(messageString, Command.Send);
                    //string message = ReceiveMessage();
                    //if (message == "503")
                    //{
                    //    MessageBox.Show("Couldn't send your message");
                    //}
                }
                newForm.Dispose();
            }
        }

        private void replyButton_Click(object sender, EventArgs e)
        {
            string to = getSender();
            NewMessage newForm = new NewMessage(to);
            //System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(startForm));
            if (newForm.ShowDialog() == DialogResult.OK)
            {
                foreach (string Recepient in newForm.Recepients)
                {
                    ConnectToServer();
                    string messageString = "mail#" + _login + "@" + _domain + "#" + Recepient + "#" + newForm.Subject + "#" +
                                           newForm.Message + "#";
                    SendMessage(messageString, Command.Send);
                    string message = ReceiveMessage();
                    if (message == "503")
                    {
                        MessageBox.Show("Couldn't send your message");
                    }
                    DisconnectFromServer();
                }
                newForm.Dispose();
            }
        }

        private List<int> getIds()
        {
            List<int> result = new List<int>();
            for (int i = 0; i < inboxGrid.RowCount; i++)
            {
                if (inboxGrid[0, i].Selected)
                {
                    if (currentFolder == Folder.Inbox)
                    {
                        result.Add(_messages[i].Id);
                    }
                    else if (currentFolder == Folder.Junk)
                    {
                        result.Add(_junkMessages[i].Id);
                    }
                }
            }
            return result;
        }

        private void mtjButton_Click(object sender, EventArgs e)
        {
            List<int> idList = getIds();
            string message;
            foreach (int id in idList)
            {
                
                message = "mtj#" + id.ToString() + "#" + _login + "#";
                SendMessage(message, Command.MoveToJunk);
            }
            UpdateGrid();
        }

        private void deleteButton_Click(object sender, EventArgs e)
        {
            try
            {
                List<int> idList = getIds();
                string message;
                foreach (int id in idList)
                {
                    ConnectToServer();
                    message = "del#" + id.ToString() + "#" + _login + "#";
                    SendMessage(message, Command.Delete);
                }
            }
            catch (Exception ex)
            {
                switch (ex.Message)
                {
                    case "503":
                        MessageBox.Show("Cannot delete selected message(s)");
                        break;
                    default:
                        MessageBox.Show(ex.Message);
                        break;
                }
            }
        }

        private void selectButton_Click(object sender, EventArgs e)
        {
            inboxGrid.SelectAll();
        }

        private void deselectButton_Click(object sender, EventArgs e)
        {
            inboxGrid.ClearSelection();
        }

        private void refreshButton_Click(object sender, EventArgs e)
        {
            try
            {
                string message = "chck#" + _login + "#";
                SendMessage(message, Command.Refresh);
            }
            catch
            {
            }
        }

        private void goToButton_Click(object sender, EventArgs e)
        {
            if (currentFolder == Folder.Inbox)
            {
                currentFolder = Folder.Junk;
                UpdateJunkGrid();
                goToButton.Text = "Go to Inbox";
            }
            else if (currentFolder == Folder.Junk)
            {
                currentFolder = Folder.Inbox;
                UpdateGrid();
                goToButton.Text = "Go to Junk";
            }
        }

        private Message ParseMsg(string message)
        {
            string[] splitMessage = message.Split(new Char[] { '#' });
            Message tempMessage = new Message
                {
                    From = splitMessage[0],
                    Subject = splitMessage[0 + 2],
                    MessageContent = splitMessage[0 + 3]
                };
            return tempMessage;
        }

        private void inboxGrid_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex >= 0)
            {
                if (currentFolder == Folder.Inbox)
                {
                    string msg = "msg#" + _messages[e.RowIndex].Id.ToString() + "#" + _login + "#";
                    SendMessage(msg, Command.GetMessage);
                    if (_messages[e.RowIndex].State == States.Unread)
                    {
                        _messages[e.RowIndex].State = States.Read;
                        DataGridViewCellStyle cellStyle = inboxGrid.DefaultCellStyle;
                        Font regularFont = cellStyle.Font;
                        inboxGrid[0, e.RowIndex].Style.Font = regularFont;
                        inboxGrid[1, e.RowIndex].Style.Font = regularFont;
                    }

                }
                if (currentFolder == Folder.Junk)
                {
                    SendMessage("msg#" + _junkMessages[e.RowIndex].Id.ToString() + "#" + _login + "#", Command.GetMessage);
                }
            }
        }

        private void Inbox_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(!LoggedOut)
            {
                    e.Cancel = true;
                    LogOut();
                
            }
        }

        private void exitButton_Click(object sender, EventArgs e)
        {
            LogOut();
        }
        
    }

    public class SocketStateObject
    {
        public const int BUF_SIZE = 1024;
        public byte[] m_DataBuf = new byte[BUF_SIZE];
        public StringBuilder m_StringBuilder = new StringBuilder();
        public Socket m_SocketFd = null;
        public Command m_cmd = Command.None;
        public string m_Message = null;
    }
}
